<?php

/**
 * The file that defines the core WXTPP class
 *
 *
 * @link       https://git.oschina.net/wostech/wp-wxtpp
 * @since      1.0.0
 *
 * @package    WP_WXTPP
 * @subpackage WP_WXTPP/includes
 */

/**
 * The core WXTPP (WeiXin Third-Party Platform) class.
 *
 * @since      1.0.0
 * @package    WP_WXTPP
 * @subpackage WP_WXTPP/includes
 * @author     Yajun Hu <yjhu@126.com>
 */
class WXTPP {
        const SETTINGS_OPTION_NAME = 'wxtpp_settings';
        const PREAUTHCODE_TRANSIENT_NAME = 'wxtpp_pre_auth_code';

	/**
	 * WXTPP settings
	 *
	 * @since    1.0.0
	 * @access   protected
	 * @var      string    $appId    WXTPP AppID.
         * @var      string    $appSecret    WXTPP AppSecret.
         * @var      string    $token    WXTPP token.
         * @var      string    $encodingAesKey    WXTPP encodingAesKey.
	 */
        protected $settings;
	protected $appId;
        protected $appSecret;
        protected $token;
        protected $encodingAesKey;
        protected $component_verify_ticket;
        protected $component_access_token;
        protected $component_access_token_expires_in;

	/**
	 * construct function.
	 *
	 * @since    1.0.0
	 */
	private function __construct() {
            require_once plugin_dir_path( dirname( __FILE__ ) ) . 'includes/class-wxtpp-gh.php';
            require_once plugin_dir_path( dirname( __FILE__ ) ) . 'libs/wx/wxBizMsgCrypt.php';
                        
            $this->settings = get_blog_option( 1, self::SETTINGS_OPTION_NAME );
            $this->appId = isset( $this->settings[ 'appId' ] ) ? $this->settings[ 'appId' ] : NULL;
            $this->appSecret = isset( $this->settings[ 'appSecret' ] ) ? $this->settings[ 'appSecret' ] : NULL;
            $this->token = isset( $this->settings[ 'token' ] ) ? $this->settings[ 'token' ] : NULL;
            $this->encodingAesKey = isset( $this->settings[ 'encodingAesKey' ] ) ? $this->settings[ 'encodingAesKey' ] : NULL;
            $this->component_verify_ticket = isset( $this->settings[ 'component_verify_ticket' ] ) ? $this->settings[ 'component_verify_ticket' ] : NULL;
            $this->component_access_token = isset( $this->settings[ 'component_access_token' ] ) ? $this->settings[ 'component_access_token' ] : NULL;
            $this->component_access_token_expires_in = isset( $this->settings[ 'component_access_token_expires_in' ] ) ? $this->settings[ 'component_access_token_expires_in' ] : NULL;
            if ( ! is_null($this->component_verify_ticket) ) {
                $this->get_component_access_token();
            }
            
            $this->crypt = new WXBizMsgCrypt( $this->token, $this->encodingAesKey, $this->appId );
	}
        
        /**
	 * process WXTPP messages function.
	 *
	 * @since    1.0.0
	 * @param WP_REST_Request $request Full data about the request.
	 * @return WP_Error|array WXTPP response data or WP_Error on failure.
	 */
	public function process( $request ) {
            $signature  = $request->get_param( 'signature' );
            $timestamp  = $request->get_param( 'timestamp' );
            $nonce      = $request->get_param( 'nonce' );
            
            if ( $this->check_signature($signature, $timestamp, $nonce) ) {
                $echostr = $request->get_param( 'echostr' );
                if ( ! empty( $echostr ) ) {
                    die( $echostr );
                }

                $msg_arr = $this->decrypt_msg( $request );

                if ( FALSE !== $msg_arr ) {
                    switch ( $msg_arr['InfoType'] ) {
                        case 'component_verify_ticket':
                            $this->process_component_verify_ticket( $msg_arr );
                            break;
                        
                        case 'authorized':
                            $this->process_authorized( $msg_arr );
                            break;
                        
                        case 'unauthorized':
                            $this->process_unauthorized( $msg_arr );
                            break;
                        
                        case 'updateauthorized':
                            $this->process_updateauthorized( $msg_arr );
                            break;
                    }
                }
                
            }
            
            die( 'success' );
	}
        
        public function encrypt_msg( $msg ) {
            $encrypt_msg = '';
            $errcode = $this->crypt->encryptMsg( $msg, time(), wp_create_nonce( 'wxtpp' ), $encrypt_msg );
            if ( 0 === $errcode ) {
                return $encrypt_msg;
            } else {
                return FALSE;
            }
        }
        
        public function decrypt_msg( $request ) {
//            require_once plugin_dir_path( dirname( __FILE__ ) ) . 'libs/wx/wxBizMsgCrypt.php';
//            $crypt = new WXBizMsgCrypt( $this->token, $this->encodingAesKey, $this->appId );
            
            $msg_signature  = $request->get_param( 'msg_signature' );
            $timestamp      = $request->get_param( 'timestamp' );
            $nonce          = $request->get_param( 'nonce' );
            $encrypt_msg    = $request->get_body();
            
            $decrypt_msg = '';
            $errcode = $this->crypt->decryptMsg( $msg_signature, $timestamp, $nonce, $encrypt_msg, $decrypt_msg );
            
            if ( 0 === $errcode ) {
                $tmpobj = simplexml_load_string($decrypt_msg, 'SimpleXMLElement', LIBXML_NOCDATA);
                $msg_arr = json_decode(json_encode($tmpobj), true);
                
                return $msg_arr; 
            } else {
                return FALSE;
            }
        }

	/**
	 * check_signature.
	 *
	 * @since    1.0.0
	 */
	public function check_signature( $signature, $timestamp, $nonce) {
            $tmpArr = array( $this->token, $timestamp, $nonce );
            sort($tmpArr, SORT_STRING);
            $tmpStr = implode($tmpArr);
            $tmpStr = sha1($tmpStr);
            return $tmpStr == $signature? true : false;               
	}
        
        /**
	 * process_component_verify_ticket.
	 *
	 * @since    1.0.0
	 */
	private function process_component_verify_ticket( $msg_arr ) {   
//            error_log( print_r( $msg_arr, true ) );
            if ( is_null($this->component_verify_ticket) || $this->component_verify_ticket !== $msg_arr[ 'ComponentVerifyTicket' ] ) {
                $this->component_verify_ticket = $msg_arr[ 'ComponentVerifyTicket' ];
                $this->settings[ 'component_verify_ticket' ] = $msg_arr[ 'ComponentVerifyTicket' ];
                update_blog_option( 1, self::SETTINGS_OPTION_NAME, $this->settings );
            }
	}
        
        /**
	 * process_authorized.
	 *
	 * @since    1.0.0
	 */
	private function process_unauthorized( $msg_arr ) { 
//            error_log( print_r( $msg_arr, TRUE ) );
            $wxtpp_gh = new WXTPP_GH( $msg_arr[ 'AuthorizerAppid' ] );
            $wxtpp_gh->set_authorized( FALSE );
	}
        
        /**
	 * process_authorized.
	 *
	 * @since    1.0.0
	 */
	private function process_authorized( $msg_arr ) {   
//            error_log( print_r( $msg_arr, TRUE ) );
            $wxtpp_gh = new WXTPP_GH( $msg_arr[ 'AuthorizerAppid' ] );
            $wxtpp_gh->set_authorized( TRUE );
	}
        
        /**
	 * process_authorized.
	 *
	 * @since    1.0.0
	 */
	private function process_updateauthorized( $msg_arr ) {  
            error_log( print_r( $msg_arr, TRUE ) );
            $wxtpp_gh = new WXTPP_GH( $msg_arr[ 'AuthorizerAppid' ] );
            $wxtpp_gh->get_authorizer_info( true );
	}
        
        /**
	 * get_component_access_token.
	 *
	 * @since    1.0.0
	 */
	private function get_component_access_token( $force_loading = FALSE ) {
            if ( $force_loading || is_null( $this->component_access_token ) || time() > $this->component_access_token_expires_in ) {
                $args = [
                    'body' =>json_encode( [
                        'component_appid' => $this->appId,
                        'component_appsecret' => $this->appSecret,
                        'component_verify_ticket' => $this->component_verify_ticket,
                    ] ),
                ];
                $response = json_decode( wp_remote_retrieve_body( wp_remote_post( 'https://api.weixin.qq.com/cgi-bin/component/api_component_token', $args ) ), TRUE );
                if ( isset( $response[ 'errcode' ] ) ) {
                    $this->error_log( __METHOD__, __FILE__, __LINE__, $response);
                } else {
                    $this->component_access_token = $response[ 'component_access_token' ];
                    $this->component_access_token_expires_in = time() + $response[ 'expires_in' ] - 200;
                    $this->settings[ 'component_access_token' ] = $this->component_access_token;
                    $this->settings[ 'component_access_token_expires_in' ] = $this->component_access_token_expires_in;
                    update_blog_option( 1, self::SETTINGS_OPTION_NAME, $this->settings );
                }
            }
            return $this->component_access_token;
	}
        
        /**
	 * get_pre_auth_code.
	 *
	 * @since    1.0.0
	 */
	public function get_pre_auth_code( $force_loading = FALSE ) {
            $pre_auth_code = get_site_transient( self::PREAUTHCODE_TRANSIENT_NAME );
            if ( $force_loading || FALSE === $pre_auth_code ) {
                $component_access_token = $this->get_component_access_token();
                $args = [
                    'body' =>json_encode( [
                        'component_appid' => $this->appId,                        
                    ] ),
                ];
                $response = json_decode( wp_remote_retrieve_body( wp_remote_post( "https://api.weixin.qq.com/cgi-bin/component/api_create_preauthcode?component_access_token={$component_access_token}", $args ) ), TRUE );
                if ( isset( $response[ 'errcode' ] ) ) {
                    $this->error_log( __METHOD__, __FILE__, __LINE__, $response);
                } else {
                    $pre_auth_code = $response[ 'pre_auth_code' ];
                    set_site_transient( self::PREAUTHCODE_TRANSIENT_NAME, $pre_auth_code, $response[ 'expires_in' ] - 5 );
                }
            }
            return $pre_auth_code;
	}
        
        public function clear_pre_auth_code() {
            delete_site_transient( self::PREAUTHCODE_TRANSIENT_NAME );
        }
        
        /**
	 * api_component_query_auth.
	 *
	 * @since    1.0.0
	 */
	public function api_component_query_auth( $auth_code ) {
            $component_access_token = $this->get_component_access_token();
            $args = [
                'body' =>json_encode( [
                    'component_appid' => $this->appId,         
                    'authorization_code' => $auth_code,
                ] ),
            ];
            $response = json_decode( wp_remote_retrieve_body( wp_remote_post( "https://api.weixin.qq.com/cgi-bin/component/api_query_auth?component_access_token={$component_access_token}", $args ) ), TRUE );
            if ( isset( $response[ 'errcode' ] ) ) {
                $this->error_log( __METHOD__, __FILE__, __LINE__, $response);
                return FALSE;
            } else {
                return $response;
            }
	}
        
        /**
	 * api_component_authorizer_token.
	 *
	 * @since    1.0.0
	 */
	public function api_component_authorizer_token( $appid, $refresh_token ) {
            $component_access_token = $this->get_component_access_token();
            $args = [
                'body' =>json_encode( [
                    'component_appid' => $this->appId,         
                    'authorizer_appid' => $appid,
                    'authorizer_refresh_token' => $refresh_token,
                ] ),
            ];
            $response = json_decode( wp_remote_retrieve_body( wp_remote_post( "https://api.weixin.qq.com/cgi-bin/component/api_authorizer_token?component_access_token={$component_access_token}", $args ) ), TRUE );
            if ( isset( $response[ 'errcode' ] ) ) {
                $this->error_log( __METHOD__, __FILE__, __LINE__, $response);
                return FALSE;
            } else {
                return $response;
            }
	}
        
        /**
	 * api_component_get_authorizer_info.
	 *
	 * @since    1.0.0
	 */
	public function api_component_get_authorizer_info( $appid ) {
            $component_access_token = $this->get_component_access_token();
            $args = [
                'body' =>json_encode( [
                    'component_appid' => $this->appId,         
                    'authorizer_appid' => $appid,
                ] ),
            ];
            $response = json_decode( wp_remote_retrieve_body( wp_remote_post( "https://api.weixin.qq.com/cgi-bin/component/api_get_authorizer_info?component_access_token={$component_access_token}", $args ) ), TRUE );
            if ( isset( $response[ 'errcode' ] ) ) {
                $this->error_log( __METHOD__, __FILE__, __LINE__, $response);
                return FALSE;
            } else {
                return $response;
            }
	}
        
        /**
	 * api_component_get_authorizer_option.
	 *
	 * @since    1.0.0
	 */
	public function api_component_get_authorizer_option( $appid, $option_name ) {
            $component_access_token = $this->get_component_access_token();
            $args = [
                'body' =>json_encode( [
                    'component_appid' => $this->appId,         
                    'authorizer_appid' => $appid,
                    'option_name' => $option_name,
                ] ),
            ];
            $response = json_decode( wp_remote_retrieve_body( wp_remote_post( "https://api.weixin.qq.com/cgi-bin/component/api_get_authorizer_option?component_access_token={$component_access_token}", $args ) ), TRUE );
            if ( isset( $response[ 'errcode' ] ) ) {
                $this->error_log( __METHOD__, __FILE__, __LINE__, $response);
                return FALSE;
            } else {
                return $response;
            }
	}
        
        /**
	 * api_component_set_authorizer_option.
	 *
	 * @since    1.0.0
	 */
	public function api_component_set_authorizer_option( $appid, $option_name, $option_value ) {
            $component_access_token = $this->get_component_access_token();
            $args = [
                'body' =>json_encode( [
                    'component_appid' => $this->appId,         
                    'authorizer_appid' => $appid,
                    'option_name' => $option_name,
                    'option_value' => $option_value,
                ] ),
            ];
            $response = json_decode( wp_remote_retrieve_body( wp_remote_post( "https://api.weixin.qq.com/cgi-bin/component/api_set_authorizer_option?component_access_token={$component_access_token}", $args ) ), TRUE );
            if ( isset( $response[ 'errcode' ] ) && 0 === $response[ 'errcode' ] ) {
                $this->error_log( __METHOD__, __FILE__, __LINE__, $response);
                return FALSE;
            } else {
                return TRUE;
            }
	}
        
        /**
	 * get_component_appid.
	 *
	 * @since    1.0.0
	 */
	public function get_component_appid() {            
            return $this->appId;
	}
        
        /**
	 * process_authorized.
	 *
	 * @since    1.0.0
	 */
	private function error_log( $method, $file, $line, $response ) {
            error_log( "wxtpp api failed ({$method} at {$file}#{$line}): errcode = {$response[ 'errcode' ]}, errmsg = {$response[ 'errmsg' ]}" );
	}
        
        public static function get_instance() {
            static $_self;
            if ( empty($_self) ) {
                $_self = new self();
            }
            return $_self;
        }
}
